﻿using System;
using System.Collections.Generic;
using System.Text;
using Rites.GameObjects;
using Rites.Networking;

namespace Rites
{
    class ServerGame
    {
        private ConnectHandler listener;            // This accepts new client connections for the server
        public Dictionary<int, ServerPlayer> _players = new Dictionary<int, ServerPlayer>(); //Indexed master list of players

        public void StartNetGame()
        {
            this.listener = new ConnectHandler(Constants.PORT);
            this.listener.userAdded += new UserEventDlg(this.PlayerHasJoined);
            this.listener.Start();
        }

        //New player has connected
        private void PlayerHasJoined(object sender, ServerPlayer joiningPlayer)
        {
            //Register delegates for the player for comms
            joiningPlayer.DataReceived += new DataReceivedDlg(this.DataReceivedFromPlayer);
            joiningPlayer.UserDisconnected += new UserEventDlg(this.PlayerDisconnectedFromServer);

            //Tell the new player what what his global ID is
            //We never use it as an authoritative source of info when it comes from the client
            joiningPlayer.SendMessage(RitesProtocol.PlayerID, joiningPlayer.ID.ToString());

            //Send all other connected players information that this player has connected
            SendToAllPlayers(RitesProtocol.UserConnected, joiningPlayer.ID.ToString());

            //Add the new player to the master list of players
            _players.Add(joiningPlayer.ID, joiningPlayer);
        }

        //This sends the message passed in to all connected players
        private void SendToAllPlayers(RitesProtocol messageType, string messageData)
        {
            long lastSentMessageID;
            System.IO.MemoryStream memStream = Util.CreateMessage(messageType, messageData, out lastSentMessageID, 0);

            foreach (ServerPlayer plr in _players.Values)
            {
                plr.SendMemoryStream(memStream);
                plr.LastSendMessageID = lastSentMessageID;
            }
        }

        //Player disconnected
        private void PlayerDisconnectedFromServer(object sender, ServerPlayer playerComm)
        {
            //Remove the disconnected player from the list of players and inform others
            _players.Remove(playerComm.ID);
            SendToAllPlayers(RitesProtocol.UserDisconnected, playerComm.ID.ToString());
        }

        private void DataReceivedFromPlayer(ServerPlayer sourcePlayer, byte[] data)
        {
            //Convert to string
            string rawBuffer = System.Text.Encoding.ASCII.GetString(data);

            //Add to the relevant player's data buffer
            sourcePlayer.DataBuffer += rawBuffer;

            //Process the buffer, search for messages that have header and footer intact, only process complete messages
            int messageStart = sourcePlayer.DataBuffer.IndexOf(Constants.BOM);
            int messageStop = sourcePlayer.DataBuffer.IndexOf(Constants.EOM);

            while (messageStart >= 0 && messageStop >= 0)
            {
                //Copy complete message from buffer
                string receivedData = sourcePlayer.DataBuffer.Substring(messageStart + Constants.BOM.Length, messageStop - (messageStart + Constants.BOM.Length));

                //Remove message from buffer
                //Note: This will remove requests that are exact duplicates, this might be bad - or good, not sure
                sourcePlayer.DataBuffer = sourcePlayer.DataBuffer.Replace(Constants.BOM + receivedData + Constants.EOM, "");

                //Process this extracted message
                ProcessMessageFromPlayer(sourcePlayer, receivedData);

                //Find next complete message
                messageStart = sourcePlayer.DataBuffer.IndexOf(Constants.BOM);
                messageStop = sourcePlayer.DataBuffer.IndexOf(Constants.EOM);
            }
        }

        private void ProcessMessageFromPlayer(ServerPlayer sourcePlayer, string playerMessage)
        {
            //Message Format:
            //<MSG_TYPE> ~ <MSG_ID> ~ <MSG_DATA_PARAMETER#MSG_DATA_PARAMETER#MSG_DATA_PARAMETER...>

            //Extract message parameters
            string[] metaParams = playerMessage.Split('~');
            long messageID = long.Parse(metaParams[1]);
            playerMessage = metaParams[2];
            string[] dataParams = playerMessage.Split('#');

            //Process based on the type of message
            switch ((RitesProtocol)(int.Parse(metaParams[0])))
            {
                case RitesProtocol.Chat: //A chat text from the client
                    //Later on we might validate this so you can't chat to people that are not in the same place as you, but this is global for now
                    SendToAllPlayers(RitesProtocol.Chat, sourcePlayer.ID.ToString() + "#" + metaParams[2].ToString());
                    break;
                case RitesProtocol.PlayerList: //This is a request from the client for a list of players
                    string userList = string.Empty;

                    foreach (ServerPlayer plr in _players.Values)
                        if (plr.ID != sourcePlayer.ID)
                            userList += plr.ID + "#";

                    if (userList.Length > 0)
                        userList = userList.Substring(0, userList.Length - 1);
                    
                    //Send player list, which is just a list of IDs
                    sourcePlayer.SendMessage(RitesProtocol.PlayerList, userList);

                    //Now we send complete player info, per player ID issued before
                    foreach (ServerPlayer plr in _players.Values)
                        if (plr.ID != sourcePlayer.ID)
                            sourcePlayer.SendMessage(RitesProtocol.PlayerUpdate, plr.ID + "#" + plr.Name);

                    break;
                case RitesProtocol.PlayerUpdate: //This is a request from the client, to update their player
                    //TODO: This would need to be heavily validated, players could send updates in with +500hp to their chars. Everything must be validated.
                    //Only name for now, this needs to be fleshed out later
                    sourcePlayer.Name = dataParams[0];

                    //Update all client of the change, including the client that sent the request
                    SendToAllPlayers(RitesProtocol.PlayerUpdate, sourcePlayer.ID.ToString() + "#" + sourcePlayer.Name);
                    break;
                case RitesProtocol.UserDisconnected: //Notification from client of a disconnect
                    SendToAllPlayers(RitesProtocol.UserDisconnected, sourcePlayer.ID.ToString());
                    sourcePlayer.Disconnect();
                    break;
            }
        }
    }
}
